/*
 * This file is part of Sparwe.
 *
 *  Sparwe is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Sparwe is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Sparwe.  If not, see <http://www.gnu.org/licenses/>.
 */ 


#ifndef __QUEUE_H__
#define __QUEUE_H__

#include <cassert>
#include <boost/shared_ptr.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include "exceptions.h"

namespace sp_core
{

	/** 
	 * @brief A QueueNode which is stored in the sp_core::Queue.
	 */
	template<typename T> class QueueNode
	{
		public:
			/** 
			 * @brief Initializes the node.
			 * 
			 * @param obj 
			 */
			QueueNode(T obj) :
				data(obj), mpNext(NULL)
			{
			}

			/** 
			 * @brief 
			 */
			virtual ~QueueNode()
			{
				if (mpNext != NULL)
					delete mpNext;

				
			}

			/** 
			 * @brief Returns the data. 
			 * 
			 * @return Data object in node.
			 */
			T GetData() const
			{
				return data;
			}

			/** 
			 * @brief Returns the next node.
			 * 
			 * @return Pointer to the next node.
			 */
			QueueNode<T>* GetNext() const
			{
				return mpNext;
			}

			/** 
			 * @brief Sets the next node to node.
			 * 
			 * @param node New next node.
			 */
			void SetNextNode(QueueNode<T>* node)
			{
				mpNext = node;
			}

			/** 
			 * @brief Creates the next node from a data object.
			 * 
			 * @param data Data to be used in the next node.
			 */
			void CreateNextNode(T data)
			{
				mpNext = new QueueNode<T>(data);
			}

		private:

			T data;
	
			QueueNode<T>* mpNext;
	};



	/** 
	 * @brief A thread safe queue, T must be a pointer.. 
	 */
	template<typename T> 
	class Queue
	{
		public:
			/** 
			 * @brief Initializes the queue.
			 */
			Queue() : 
				mpHead(NULL), mpTail(NULL)
			{
				
			}

			/** 
			 * @brief Deletes the queue.
			 */
			virtual ~Queue()
			{
				QueueNode<T>* current = mpHead;

				delete mpHead;
				
			}

			/** 
			 * @brief Returns the head object.
			 *
			 * @pre The queue is not empty.
			 * @post head object is returned.
			 * 
			 * @return Head object.
			 */
			T Peek() const
			{
				boost::mutex::scoped_lock lock(mMutex);
				
				ValidateHead();

				return mpHead->GetData();
			}

			/** 
			 * @brief Returns the head object and sets the following node as head.
			 * 
			 * @pre The queue is not empty.
			 * @post returned head object and head = head->next.
			 *
			 * @return Head object.
			 */
			T Pull()
			{
				boost::mutex::scoped_lock lock(mMutex);

				ValidateHead();

				if (mpHead->GetData() == NULL)
				{
					throw Exception();
				}

				T ret = mpHead->GetData();
				mpHead = mpHead->GetNext();
				
				return ret;
			}

			/** 
			 * @brief Inserts the object in the tail of the queue.
			 * 
			 * @pre obj != NULL
			 * @post mpTail == ObjectNode<T>(obj)
			 *
			 * @param obj 
			 */
			void Push(T obj)
			{
				boost::mutex::scoped_lock lock(mMutex);

				QueueNode<T>* newTail = new QueueNode<T>(obj);
				if (mpHead == NULL)
				{
					mpHead = newTail;
				}

				if (mpTail == NULL)
				{
					mpTail = newTail;
				}
				else
				{
					PushBack(newTail);
				}

			}

		private:

			/** 
			 * @brief Validates if the node object data of node.
			 * 
			 * @pre
			 * @post
			 *
			 * @param node 
			 */
			void ValidateNode(QueueNode<T>* node) const
			{
				if (node == NULL)
				{
					throw Exception();
				}
			}

			/** 
			 * @brief Validates if the head object is valid.
			 */
			void ValidateHead() const
			{
				ValidateNode(mpHead);
			}

			/** 
			 * @brief Validates if the tail object is valid.
			 */
			void ValidateTail() const
			{
				ValidateNode(mpTail);
			}

			/** 
			 * @brief Validates if the queue has any broken links.
			 */
			void ValidateQueue() const
			{
				QueueNode<T>* current = mpHead;

				do
				{
					if (current->GetNext() == NULL) // Broken queue if the next node is NULL and not tail.
					{
						throw Exception();
					}
				} 
				while (current != mpTail);

			}

			/** 
			 * @brief Inserts the node pointer at the last position in the queue. 
			 * @pre node is allocated on the heap.
			 * @post mpTail == node
			 * 
			 * @param node 
			 */
			void PushBack(QueueNode<T>* node)
			{
				mpTail->SetNextNode(node);
				mpTail = node;
			}


			static boost::mutex mMutex;
			QueueNode<T>* mpHead;
			QueueNode<T>* mpTail;

	};

	template <typename T> boost::mutex Queue<T>::mMutex;
}



#endif
